Skip to content

chore(electron): 34 → 44 (Chromium 132 → 152, Node 20 → 24) - #148

Merged
CSSFrancis merged 8 commits into
mainfrom
chore/electron-44
Aug 31, 2026
Merged

chore(electron): 34 → 44 (Chromium 132 → 152, Node 20 → 24)#148
CSSFrancis merged 8 commits into
mainfrom
chore/electron-44

Conversation

@CSSFrancis

@CSSFrancis CSSFrancis commented Aug 28, 2026

Copy link
Copy Markdown
Member

Electron 34 is ten majors behind and long past end of life. Support covers the latest three majors, so 34.5.8 is the final release of that line and will never be patched again — ~20 Chromium majors of unfixed CVEs in an app that renders HTML figures and fetches example data over the network.

before after
Electron 34.5.8 44.0.0
Chromium 132 152
Node 20.19.1 24.18.1

Pinned exactly in both package.json files, which must agree — see d70f4fb. The workspace hoists electron out of electron/node_modules, so electron-builder can't read the installed version, falls back to parsing the spec, and refuses a range. That failure is release-only and is what killed the v0.4.0 tag build.

Application changes: three

  • printToPDF's marginType is gone. The 'default' it named is now what you get by omitting margins entirely (1 cm a side), so the fix is a deletion.
  • clipboard.writeImage is gone; the module is rearchitected to the W3C shape — MIME-typed ClipboardItems, all async. The nativeImage decode stays as the validity check that keeps junk off the user's clipboard.
  • A new window is no longer PLACED on top of an open caret. findFreeSlot packs a window into the first slot colliding with no existing window and knew nothing about carets, so a fit / DPC / strain / orientation run dropped its own result window onto the panel that launched it. Z-order is untouched — a window still comes to the front over a caret it overlaps, and the caret stays open underneath.

Everything else across ten majors was checked and found unused here: Unity/Linux badge + progress APIs, pre-macOS-13 login-item attributes, win32-ia32/linux-armv7l, renderer-side clipboard, ANGLE, webRequest filters, setPreloads, webFrame.routingId, desktopCapturer, session extension APIs.

console-message still works (the WebContents overload keeps emitting positional args) but level was remapped, so the comment describing it is corrected: 0=verbose 1=info 2=warning 3=error.

CI gains one step

Electron 42 dropped the postinstall binary download, so npm ci leaves node_modules/electron/dist empty and the ~100 MB fetch happens lazily on the first require('electron') — in CI, inside the first spec that launches the app, where a slow or failed download reads as a mystery test timeout. npx install-electron makes it fail loudly on its own line.

The e2e work: nothing regressed, the tests lacked a reflex

29 e2e tests went red on Linux. Every one traced to a window covering the control — which is intended behaviour. A person clicks the window and carries on; a spec keeps clicking a covered point until it times out.

Cause Fix
New window placed onto an open caret app: placement treats carets as occupied
Caret / view chip / toolbar button covered raiseWindow — do what the user does
The raise itself blocked, titlebar also covered dispatch mousedown (SubWindow raises on it) rather than click
Crosshair drags landing on the wrong window same raise
Stale private copies of the WebGPU/dbus noise filter use the shared backendErrorLines
Unwaited report-cell helpers wait for the state each step depends on

Two worth calling out:

  • find_vectors_5d reported the marker moving 8px of a required 10. That reads exactly like a threshold to relax — and relaxing it would have buried a drag landing on the wrong window entirely. A drag gets no actionability check, so interception shows up as a short move, not a timeout.
  • 55f471f and f10c27e are a false start, kept in history rather than squashed. They raised the caret's z-index and then pinned the owning window on top. The first was inert (a child cannot escape its parent's stacking context — measured: a caret at z-index 1002 lost to a window at 11), and the second changed behaviour nobody asked to change. 695cf07 reverts both and fixes it at placement.

Verification

  • Full local suite on macOS: 422 passed
  • CI: all 10 e2e shards green, 35 checks passing
  • Packaging: npm run dist:dir → 348 MB SpyDE.app carrying Electron Framework 44.0.0. This is the check that does not otherwise run until a tag build — and is how the v0.4.0 release broke
  • GPU parity: meanDiff: 0, maxDiff: 2 between the WebGPU and Canvas2D renders, screenshots compared by eye. Worth having across Chromium 132 → 152
  • caret_placement.spec.ts was verified to FAIL without the fix (99% of the caret buried) before being trusted to pass with it

Unrelated, found on the way

spyde/backend/dask_stats.py:113 calls client.run(_trim_process_memory, wait=False), which trips distributed 2026.3.0's assert wait or is_coro. Every post-batch worker memory trim fails and logs three tracebacks; only the backend's own local trim runs. is_coro also satisfies the assert, so an async def wrapper restores the deliberate fire-and-forget. Not touched here — worth its own PR.

Electron 34 is ten majors behind and long past end of life: support covers
the latest three majors, so 34.5.8 is the final release of that line and will
never be patched again. This carries ~20 Chromium majors of unfixed CVEs in an
app that renders HTML figures and fetches example data over the network.

Chromium 132 -> 152, Node 20.19 -> 24.18.

Pinned EXACTLY in BOTH package.json files, which must agree -- see d70f4fb.
The workspace hoists electron out of electron/node_modules, so electron-builder
cannot read the installed version, falls back to parsing the spec, and refuses
a range. That failure is release-only.

Only two API breaks across ten majors, both caught by typecheck:

  - printToPDF's `marginType` is gone. The 'default' it named is now what you
    get by omitting `margins` entirely (1cm a side), so the fix is a deletion.
  - clipboard.writeImage is gone; the module is rearchitected to the W3C shape
    (MIME-typed ClipboardItems, async). The nativeImage decode stays as the
    validity check that keeps junk off the user's clipboard.

report_export.spec.ts asserted the OS clipboard through readImage(), removed
in the same change, so it now asks whether the clipboard holds a non-empty
image/png.

CI gains an explicit `npx install-electron`: Electron 42 dropped the postinstall
binary download, so `npm ci` leaves node_modules/electron/dist empty and the
~100 MB fetch happens lazily on the first require('electron') -- which in CI is
inside the first spec that launches the app, where a slow or failed download
reads as a mystery test timeout rather than an install failure.

Swept the rest of the 35-44 breaking changes against this codebase: Unity and
Linux badge/progress APIs, pre-macOS-13 login item attributes, win32-ia32 and
linux-armv7l targets, renderer-side clipboard, ANGLE, webRequest filters,
setPreloads, webFrame.routingId, desktopCapturer, session extension APIs. None
are used here.

console-message keeps working -- the WebContents overload still emits the
positional arguments, deprecated but present -- but `level` was remapped, so
the comment describing it was corrected: 0=verbose 1=info 2=warning 3=error,
which makes `level >= 2` warnings-and-errors rather than errors-and-infos.

e2e: 422 passed / 12 failed / 22 skipped over the full suite. Two of those
failures were real and are fixed here (the clipboard assertion above, plus a
stale local Playwright browser cache). The remaining ten reproduce on a quiet
machine and are being checked against main before any claim about them.
Under Electron 44 the three cells came back as ['Beta', empty, empty] --
deterministically, 3 runs out of 3, while passing on 34.

Not an app bug. Every step of the helper addresses "the last cell", so each is
wrong the moment it runs early: a dblclick before the new cell mounts opens the
PREVIOUS cell's editor, and returning before the commit renders lets the next
call race it. A probe driving the same gestures with waits passes on 44, and
the screenshot shows Alpha/Beta/Gamma committed correctly -- the feature works,
the assumption that a click had landed by the next line did not.

Chromium 152 just changed the timing enough to stop the assumption holding.
CI on Linux failed 29 e2e tests under Electron 44 that pass on 34, almost all
of them a caret click timing out with

  <iframe ...> from <div data-testid="subwindow"> subtree intercepts pointer
  events

The screenshot shows why: a subwindow is painted over the open wizard, clipping
its "+ Component" button, so the click lands on the window's figure iframe.

This is OUR bug, not Electron's. MDI windows take `10 + focusOrderIndex`, which
grows with the number of windows; WizardShell took a fixed 14. At five windows
a focused one ties the wizard and wins. The fit workflow opens navigator,
signal, "Fit components (live)", "chi squared" and the component maps, so it
crosses that line routinely. Electron 44 did not change stacking -- it changed
window count/ordering/timing enough to cross it. Anyone with five windows open
could already bury a caret under one.

So state the layering instead of leaving it to chance: windows own
1..MDI_Z_CEILING (899, clamped), and everything that must float above them --
wizard shells, floating toolbars, caret popovers -- starts at CHROME_Z (1000),
keeping the relative order it already had.

Also two test-side fixes for the same CI run:

  - backendErrorLines ignored "Failed to create WebGPU Context Provider" but
    Chromium 152 says "No available adapters" for the same missing-GPU
    condition, so three "no tracebacks" audits started failing on a headless
    runner having no GPU. Match both wordings; the comment already warned that
    an Electron bump must not turn infrastructure noise into a fake backend
    error, and this is that warning coming true a second time.

  - report_reorder built its markdown cells with the same unwaited
    click -> dblclick -> fill -> commit sequence already fixed in
    report_delete_undo (1dbc143).

Verified locally: fit_wizard, action_scoping, mdi_overlay, report_reorder and
report_delete_undo all pass. The failure itself only reproduces on Linux, so CI
is the real check.
The previous commit raised the caret's own z-index and did nothing, because a
caret cannot reach above its window from inside it. `FloatingToolbar` renders
inside `SubWindow`, whose root is positioned with a z-index and therefore forms
a stacking context; everything within is confined to that window's level.
Measured, once the overlap is forced locally:

  caret   z-index 1002   at (428,424)
  window  z-index   11   covers that point
  elementFromPoint    -> IFRAME:figure-56310c3e

1002 losing to 11 is only possible inside a trapped subtree. So keep the OWNING
window on top instead: a window with an open caret takes MDI_Z_CEILING, which
is the one thing that actually keeps the caret reachable.

This is what reddened ~25 e2e tests on the Electron 44 branch. Fit, DPC, strain
and orientation all open result windows AFTER their caret; each new window took
focus, got a higher z-index, covered the caret it was launched from, and its
figure iframe swallowed the clicks -- "iframe ... intercepts pointer events".
Nothing about it is Electron-specific: the upgrade only changed which windows
landed where. Any user who opened a result window over an open caret already
had an unclickable panel.

caret_above_windows.spec.ts pins the contract and reproduces the bug on any
platform by forcing the overlap rather than hoping for it. It asserts through
elementFromPoint, which is the question a click actually asks, and guards the
setup: if no window ends up covering the control, the spec FAILS rather than
passing without testing anything (it caught exactly that while being written --
grabbing a titlebar's centre starts an HTML5 drag instead of moving the window).

Local sweep: caret_above_windows, fit_wizard, action_scoping, mdi_layout and
mdi_overlay all pass. ui_fixes "6: Rebin" still fails here, as it does on main
with Electron 34 on this machine -- environmental, not from this change.
ipf_perf carried its own copy of backendErrorLines. The copy knew only the
parenthesised `bus.cc(406)` stderr shape and the old "Failed to create WebGPU
Context Provider" wording, so under Chromium 152 -- which writes
`dbus/bus.cc:406]` and "No available adapters" -- ordinary headless-CI noise
came back as "backend errors during IPF render".

backendErrorLines now takes either the backend or a plain array of lines, so a
spec holding its own log snapshot has no reason to fork the filter. That is the
whole point: the shared one already had both wordings, and this spec failed
only because it had drifted.
Reverts the z-order pin from f10c27e and fixes this where it actually belongs.

The pin kept a window with an open caret on top, which changed behaviour nobody
asked to change: a newly opened result window should come to the front over the
caret, and the caret should stay open underneath. That was already right.

What was wrong is PLACEMENT. findFreeSlot packs a new window into the first
slot colliding with no existing WINDOW, and knew nothing about carets — so a
fit / DPC / strain / orientation run dropped its own result window squarely
onto the panel that launched it. Measured with the caret-obstacle line disabled:
99% of the caret buried. With it: 0%.

Because a caret lives inside its window's stacking context (SubWindow's root is
positioned WITH a z-index), it cannot paint above that new window whatever
z-index it takes — a caret at z-index 1002 lost to a window at 11. So the
window's figure iframe swallowed every click meant for the caret, which is what
reddened ~18 e2e tests on the Electron 34 -> 44 upgrade.

FloatingToolbar now reports its open caret's rect in area coords (mirroring the
placement its own CSS produces; approximate for the side placements, and erring
large is safe), SubWindow relays it, and MDIArea feeds it into the taken list.
Z-order is untouched.

Also reverts the CHROME_Z / MDI_Z_CEILING constants from 55f471f. They were
inert — a child cannot escape its parent's stacking context — and their
comments asserted an explanation that is simply false, which is worse than no
comment.

caret_placement.spec.ts pins the contract and was verified to FAIL without the
fix (99% overlap) before being trusted to pass with it.
MDI windows overlap, and a window opened later sits above an earlier one --
over its toolbar, its open caret and its view chips. That is the intended
behaviour: a result window should come to the front, and the caret stays open
underneath. A person clicks the window they want and carries on, so it never
registers as a problem.

A spec has no such reflex. It keeps clicking a point that is now behind another
window until it times out, reporting "<iframe ...> intercepts pointer events".
That is what the remaining Electron 44 e2e failures were -- not a regression,
just placement moving enough that specs which had always been one unlucky
layout away from this finally hit it.

raiseWindow / raiseWindowOwning do what the user does. Both are idempotent
(raising the top window changes nothing), so they are safe to add defensively,
and raiseWindowOwning is a no-op when nothing matches rather than turning a
missing element into a confusing failure elsewhere. Neither closes an open
caret: FloatingToolbar's outside-click handler returns early for WIZARD_ACTIONS
and ignores clicks inside its own window.

All six fit specs failed at the same point -- clicking `fit-tab-Run` after
adding components, which opens the live-preview window over the caret -- so
that is one uniform insertion, not six bespoke ones. Also applied to
ipf_two_window's chip loop, ebsd_workflow's Similarity chip and ui_fixes'
Rebin button.

NB grep silently returns nothing on fit_handles.spec.ts here (ugrep), which is
why its Run-tab click looked absent; awk found it at line 235. Worth knowing
before concluding a spec does not contain something.
Two follow-ups to 91b004a, both the same root cause seen from different angles.

raiseWindow clicked the titlebar, which has to WIN hit-testing -- and the whole
reason for raising is that something is on top. ebsd_workflow found the case
where the titlebar ITSELF was covered, so the raise timed out with the very
"iframe ... intercepts pointer events" it exists to avoid. SubWindow raises on
mousedown on its root, so dispatch there instead: the same handler fires
whatever is above it. This is a SETUP step -- whatever the spec does next is
still a real hit-tested click, so nothing is papered over.

The two 5-D crosshair specs are the same problem wearing a different mask. A
drag gets no actionability check, so a navigator under another window does not
time out; it just does not move. vectors_dp_follows_nav reported the crosshair
moving 0px of a required 5, find_vectors_5d 8px of a required 10 -- which reads
like a threshold to tune and is not. Raise before dragging.
@CSSFrancis
CSSFrancis merged commit 34f8473 into main Aug 31, 2026
68 of 69 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant